// Copyright 2018 The ksonnet authors
//
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

package cluster

import (
	"encoding/json"

	"github.com/ksonnet/ksonnet/pkg/metadata"
	"github.com/pkg/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
)

// annotationCodec manages the contents of the annotation
// ksonnet adds to objects.
type annotationCodec interface {
	Encode(map[string]interface{}) error
	Decode() (map[string]interface{}, error)
	Marshal() ([]byte, error)
}

// annotationApplier tags an object with ksonnet metadata. Currently, the
// metadata includes a label and an annotation with the object's
// state as generated by ksonnet.
type annotationApplier interface {
	SetOriginalConfiguration(*unstructured.Unstructured) error
}

// defaultAnnotationApplier is the default implementation of annotationApplier.
type defaultAnnotationApplier struct {
	codec annotationCodec
}

var _ annotationApplier = (*defaultAnnotationApplier)(nil)

func newDefaultAnnotationApplier() *defaultAnnotationApplier {
	return &defaultAnnotationApplier{
		codec: &managedAnnotation{},
	}
}

func (m *defaultAnnotationApplier) SetOriginalConfiguration(obj *unstructured.Unstructured) error {
	if obj == nil {
		return errors.New("object is nil")
	}

	if m.codec == nil {
		return errors.New("encoder is nil")
	}

	if err := m.codec.Encode(obj.Object); err != nil {
		return err
	}

	mmEncoded, err := m.codec.Marshal()
	if err != nil {
		return err
	}

	SetMetaDataLabel(obj, metadata.LabelDeployManager, appKsonnet)
	SetMetaDataAnnotation(obj, metadata.AnnotationManaged, string(mmEncoded))

	return nil
}

func (m *defaultAnnotationApplier) GetOriginalConfiguration(mapping *meta.RESTMapping, obj runtime.Object) ([]byte, error) {
	annotations, err := mapping.MetadataAccessor.Annotations(obj)
	if err != nil {
		return nil, err
	}

	if annotations == nil {
		// This object might not have any annotations. This should
		// not be an error.
		return nil, nil
	}

	data, ok := annotations[metadata.AnnotationManaged]
	if !ok {
		return nil, nil
	}

	var annotation managedAnnotation
	if err = json.Unmarshal([]byte(data), &annotation); err != nil {
		return nil, errors.Wrap(err, "decoding ksonnet managed annotation")
	}

	pristineObject, err := annotation.Decode()
	if err != nil {
		return nil, err
	}

	b, err := json.Marshal(pristineObject)
	if err != nil {
		return nil, errors.Wrap(err, "encoding JSON")
	}

	return b, nil
}
